function(grpc_cpp_generate)
    cmake_parse_arguments(GRPC_GEN "" "TARGET;OUTPUT_DIR" "PROTOS" ${ARGN})

    if(NOT GRPC_GEN_TARGET)
        message(FATAL_ERROR "TARGET must be specified for grpc_generate")
    endif()

    if(NOT GRPC_GEN_PROTOS)
        message(FATAL_ERROR "PROTOS must be specified for grpc_generate")
    endif()

    if(NOT GRPC_GEN_OUTPUT_DIR)
        set(GRPC_GEN_OUTPUT_DIR ${CMAKE_CURRENT_BINARY_DIR})
    endif()

    file(MAKE_DIRECTORY ${GRPC_GEN_OUTPUT_DIR})

    set(GENERATED_SRCS)
    set(GENERATED_HDRS)

    foreach(PROTO_FILE ${GRPC_GEN_PROTOS})
        get_filename_component(PROTO_ABS ${PROTO_FILE} ABSOLUTE)
        get_filename_component(PROTO_DIR ${PROTO_ABS} PATH)
        get_filename_component(PROTO_NAME ${PROTO_ABS} NAME_WE)

        set(PB_SRC ${GRPC_GEN_OUTPUT_DIR}/${PROTO_NAME}.pb.cc)
        set(PB_HDR ${GRPC_GEN_OUTPUT_DIR}/${PROTO_NAME}.pb.h)
        set(GRPC_SRC ${GRPC_GEN_OUTPUT_DIR}/${PROTO_NAME}.grpc.pb.cc)
        set(GRPC_HDR ${GRPC_GEN_OUTPUT_DIR}/${PROTO_NAME}.grpc.pb.h)

        add_custom_command(
            OUTPUT ${PB_SRC} ${PB_HDR} ${GRPC_SRC} ${GRPC_HDR}
            COMMAND ${_PROTOBUF_PROTOC}
            ARGS --grpc_out=${GRPC_GEN_OUTPUT_DIR}
                 --cpp_out=lite:${GRPC_GEN_OUTPUT_DIR}
                 -I ${PROTO_DIR}
                 --plugin=protoc-gen-grpc=${_GRPC_CPP_PLUGIN_EXECUTABLE}
                 ${PROTO_ABS}
            DEPENDS ${PROTO_ABS}
            COMMENT "Generating gRPC code for ${PROTO_NAME}"
        )

        list(APPEND GENERATED_SRCS ${PB_SRC} ${GRPC_SRC})
        list(APPEND GENERATED_HDRS ${PB_HDR} ${GRPC_HDR})
    endforeach()

    target_sources(${GRPC_GEN_TARGET} PRIVATE ${GENERATED_SRCS} ${GENERATED_HDRS})
endfunction()